/*******************************************************************************
 * Copyright (c) 2007 University of Oslo.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     University of Oslo - initial API and implementation
 *******************************************************************************/

package com.cea.papyrus.diagram.sequence.sedi.edit.analysis;

import static com.cea.papyrus.diagram.sequence.sedi.model.util.ModelUtil.neIsInstance;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.uml2.uml.CombinedFragment;
import org.eclipse.uml2.uml.ExecutionSpecification;
import org.eclipse.uml2.uml.Interaction;
import org.eclipse.uml2.uml.InteractionFragment;
import org.eclipse.uml2.uml.InteractionOperand;
import org.eclipse.uml2.uml.Lifeline;
import org.eclipse.uml2.uml.Message;
import org.eclipse.uml2.uml.MessageOccurrenceSpecification;
import org.eclipse.uml2.uml.OccurrenceSpecification;

import com.cea.papyrus.core.utils.LookForElement;
import com.cea.papyrus.diagram.sequence.sedi.edit.SeDiSubpartEditPart;
import com.cea.papyrus.diagram.sequence.sedi.figures.LifelineFigure;
import com.cea.papyrus.diagram.sequence.sedi.model.command.LifelinePosition;
import com.cea.papyrus.diagram.sequence.sedi.model.util.ModelUtil;
import com.cea.papyrus.diagraminterchange2.di2.DiagramElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphEdge;
import com.cea.papyrus.diagraminterchange2.di2.GraphElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;

;

/**
 * Performs geometrical analysis of diagrams and makes decisions about lifeline coverage
 * by interaction fragments.
 * 
 * @author frankd@ifi.uio.no
 */
public class CoverableAnalyser {

    /**
     * The Interaction editpart. Analysis is performed only for this interaction.
     */
    private final GraphicalEditPart interactionEP;

    /**
     * Constructor.
     * 
     * @param anyEditPart
     * @param interactionView
     *            The interaction to be analyzed. This must be a NamedElementView that
     *            refers a UML2 Interaction.
     */
    public CoverableAnalyser(final EditPart anyEditPart, final GraphElement interactionView) {
        interactionEP =
                (GraphicalEditPart) anyEditPart.getViewer().getEditPartRegistry().get(
                        interactionView);
        if (interactionEP == null)
            throw new IllegalArgumentException();
    }

    @SuppressWarnings("unchecked")
    protected GraphElement getInteractionView() {
        return (GraphElement) interactionEP.getModel();
    }

    
    @SuppressWarnings("unchecked")
    private SeDiSubpartEditPart getEditPartFor(Object o) {
        return (SeDiSubpartEditPart) interactionEP.getViewer().getEditPartRegistry().get(o);
    }

    @SuppressWarnings("unchecked")
    private List<GraphElement> getDiagramContents() {
        ArrayList<GraphElement> list = new ArrayList<GraphElement>(100);
        LookForElement.getAllGraphElements(LookForElement.getDiagram(getInteractionView()), list);
        return list;
    }

    /**
     * Identifies the Lifeline that is closest to the given fragment.
     * 
     * @param fragmentView
     *            The fragment to test
     * @param bounds
     *            Optional. If given, this bounds is used rather than the bounds indicated
     *            by the fragment. This allows searches to be done without actually moving
     *            the fragment first. The bounds must be in the same coordinate system as
     *            the fragment figure.
     * @return A list containting 0 or 1 position indicators. It cannot be assumed that
     *         the interaction contains lifelines at all, in which case an empty list will
     *         be returned.
     */
    public List<LifelinePosition> findBestCovered(GraphNode fragmentView, Rectangle bounds) {

        if (bounds == null) {
            bounds = new Rectangle(fragmentView.getPosition(), fragmentView.getSize());
            GraphicalEditPart fragmentEP = getEditPartFor(fragmentView);
            fragmentEP.getFigure().translateToAbsolute(bounds);
        }

        // Select the nearest lifeline
        final GraphNode best = selectBestLifeline(bounds);
        if (best == null)
            return Collections.emptyList();

        // Find out exactly where on the lifeline
        SortedSet<SetEntry> fragments = collectFragments(best);
        SetEntry test = new SetEntry(bounds.y, false, null); // send: value irrelevant
        fragments.add(test);

        int position = 0;
        for (SetEntry setEntry : fragments) {
            if (setEntry == test) {
                final List<LifelinePosition> covered = new ArrayList<LifelinePosition>(1);
                covered.add(new LifelinePosition(ModelUtil.<Lifeline> getSemantic(best), position));
                return covered;
            }
        }
        throw new AssertionError("TestEntry not in set");
    }

    /**
     * @param bounds
     *            in absolute coordinates
     */
    @SuppressWarnings("unchecked")
    private GraphNode selectBestLifeline(final Rectangle bounds) {
        GraphNode best = null;
        int bestDistance = Integer.MAX_VALUE;

        final GraphNode interactionView = (GraphNode) getInteractionView();
        final Interaction interaction = ModelUtil.<Interaction> getSemantic(interactionView);

        for (final DiagramElement element : getDiagramContents()) {
            if (!neIsInstance(element, Lifeline.class))
                continue;

            final GraphNode lifelineView = (GraphNode) element;

            // Some diagrams can contain multiple interactions, so verify
            // that only Lifeline that belongs to the target Interaction
            // are counted
            if (ModelUtil.<Lifeline> getSemantic(lifelineView).getInteraction() == interaction) {
                final LifelineFigure figure =
                        (LifelineFigure) getEditPartFor(lifelineView).getFigure();
                final int distance = figure.getDistanceTo(bounds);
                if (distance >= 0 && distance < bestDistance) {
                    bestDistance = distance;
                    best = lifelineView;
                }
            }
        }
        return best;
    }
   
       /**
     * Identifies the Lifelines that are covered by the specified InteractionFragment
     * view.
     * 
     * @returns list of Lifeline. Returns an empty list if no lifelines are covered.
     */
    @SuppressWarnings("unchecked")
    public List<LifelinePosition> findCovered(final GraphNode fragmentView) {

        final List<LifelinePosition> covered = new LinkedList<LifelinePosition>();

        final InteractionFragment fragment =
                ModelUtil.<InteractionFragment> getSemantic(fragmentView);
        for (final DiagramElement element : getDiagramContents()) {
            if (!neIsInstance(element, Lifeline.class))
                continue;

            final GraphNode lifelineView = (GraphNode) element;
            final Lifeline lifeline = ModelUtil.<Lifeline> getSemantic(lifelineView);
            final List<InteractionFragment> coveredBy = findCoveredBy(lifelineView);
            final int index = coveredBy.indexOf(fragment);
            if (index >= 0)
                covered.add(new LifelinePosition(lifeline, index));
        }
        return covered;
    }
    
    /**
     * Identifies the Lifelines that are covered by the specified InteractionFragment
     * view.
     * 
     * @param fragmentView
     *      The fragment to test
     * @param bounds
     *		Optional. If given, this bounds is used rather than the bounds indicated
     *      by the fragment. This allows searches to be done without actually moving
     *      the fragment first. The bounds must be in the same coordinate system as
     *      the fragment figure.
     * @return list of Lifeline. Returns an empty list if no lifelines are covered.
     */
    public List<Lifeline> findCoveredLifelines(final GraphNode fragmentView, Rectangle bounds) {

        final List<Lifeline> covered = new LinkedList<Lifeline>();

        if (bounds == null) {
            bounds = new Rectangle(fragmentView.getPosition(), fragmentView.getSize());
            GraphicalEditPart fragmentEP = getEditPartFor(fragmentView);
            fragmentEP.getFigure().translateToAbsolute(bounds);
        }
             
        for (final DiagramElement element : getDiagramContents()) {
            if (!neIsInstance(element, Lifeline.class))
                continue;

            final GraphNode lifelineView = (GraphNode) element;
            Rectangle lifelineBounds = new Rectangle(lifelineView.getPosition(), lifelineView.getSize());
            GraphicalEditPart lifelineEP = getEditPartFor(lifelineView);
            lifelineEP.getFigure().translateToAbsolute(lifelineBounds);
                       
            if (bounds.contains(lifelineBounds.x, bounds.y - 1)) {
            	Lifeline lifeline = ModelUtil.getSemantic(lifelineView);
                covered.add(lifeline);
            }
        }
        
        return covered;      
    }

    /**
     * Identifies all interaction fragments that cover the given lifeline.
     * 
     * @param lifelineView
     *            The lifeline to consider.
     * @return A list referencing the interaction fragments that cover the lifeline and
     *         where on the lifeline they do so. An empty list is returned if none are
     *         found.
     */
    public List<InteractionFragment> findCoveredBy(final GraphNode lifelineView) {

        final SortedSet<SetEntry> coveredBy = collectFragments(lifelineView);

        final List<InteractionFragment> result = new LinkedList<InteractionFragment>();
        for (SetEntry e : coveredBy)
            result.add(e.getFragment());
        return result;
    }

    /**
     * Collects all interaction fragments that occur on a lifeline. The returned set
     * contains all the fragments ordered in vertical order from top to bottom, indexed by
     * their vertical coordinate.
     */
    @SuppressWarnings("unchecked")
    public SortedSet<SetEntry> collectFragments(final GraphNode lifelineView) {
        final SortedSet<SetEntry> coveredBy = new TreeSet<SetEntry>();

        final Lifeline lifeline = ModelUtil.<Lifeline> getSemantic(lifelineView);
        final LifelineFigure lifelineFigure =
                (LifelineFigure) getEditPartFor(lifelineView).getFigure();

        // final IFigure interactionFigure = interactionEP.getFigure();

        // First test for interaction fragments except EventOccurrences
        for (final GraphElement element : getDiagramContents()) {
            if (!neIsInstance(element, InteractionFragment.class)
                    || neIsInstance(element, Interaction.class)
                    || neIsInstance(element, InteractionOperand.class))
                continue;

            final GraphNode fragmentView = (GraphNode) element;
            final Rectangle fragmentBounds =
                    new Rectangle(fragmentView.getPosition(), fragmentView.getSize());

            if (!usesLocalCoords(element)) {
                IFigure fragmentFigure = getEditPartFor(fragmentView).getFigure();
                fragmentFigure.translateToAbsolute(fragmentBounds);
            }

            if (lifelineFigure.isCovered(fragmentBounds, usesLocalCoords(element))) {
                if (usesLocalCoords(element)) {
                    IFigure fragmentFigure = getEditPartFor(fragmentView).getFigure();
                    fragmentFigure.translateToAbsolute(fragmentBounds);
                }

                if (ModelUtil.getSemantic(fragmentView) instanceof ExecutionSpecification) {
                    // EOs are indirectly represented through their start and finish
                    // events.

                    ExecutionSpecification execOcc =
                            ModelUtil.<ExecutionSpecification> getSemantic(fragmentView);
                    if (execOcc.getStart() == null
                            || !(execOcc.getStart() instanceof MessageOccurrenceSpecification)
                            || ((MessageOccurrenceSpecification) execOcc.getStart()).getMessage() == null)
                        coveredBy.add(new SetEntry(fragmentBounds.y + 1, // +1 to start
                                // slightly
                                // below
                                // message
                                // received at
                                // the same
                                // position
                                false, execOcc.getStart()));
                    if (execOcc.getFinish() == null
                            || !(execOcc.getFinish() instanceof MessageOccurrenceSpecification)
                            || ((MessageOccurrenceSpecification) execOcc.getFinish()).getMessage() == null)
                        coveredBy.add(new SetEntry(fragmentBounds.bottom() - 1, // -1 to
                                // end
                                // slightly
                                // above
                                // reply
                                // message
                                // sent at
                                // the
                                // same
                                // position
                                true, execOcc.getFinish()));
                } else {
                    coveredBy.add(new SetEntry(fragmentBounds.y, false, // actual value is
                            // irrelevant in
                            // this case
                            ModelUtil.<InteractionFragment> getSemantic(fragmentView)));
                }
            }
        }

        // EventOccurrences are also InteractionFragments, but they are not
        // included in the diagramContents. So we have to test these separately.
        for (final DiagramElement element : getDiagramContents()) {
            if (!neIsInstance(element, Message.class))
                continue;

            final GraphEdge linkView = (GraphEdge) element;
            // if (linkView.getAnchorSource() != null
            // && ModelUtil.getSemantic(linkView.getAnchorSource()) == lifeline) {
            // final Point p = linkView.getAnchor().get(0).getPosition();
            // coveredBy.add(new SetEntry(p.y, true, (OccurrenceSpecification) ModelUtil
            // .<Message> getSemantic(linkView).getSendEvent()));
            // } else if (linkView.getAnchorTarget() != null
            // && ModelUtil.getSemantic(linkView.getAnchorTarget()) == lifeline) {
            // final Point p = linkView.getAnchor().get(0).getPosition();
            // coveredBy.add(new SetEntry(p.y, false, (OccurrenceSpecification) ModelUtil
            // .<Message> getSemantic(linkView).getReceiveEvent()));
            // }
            if (linkView.getAnchorSource() != null
                    && ModelUtil.getSemantic(linkView.getAnchorSource()) == lifeline) {
                final ConnectionAnchor anchor =
                        getEditPartFor(linkView.getAnchorSource()).getConnectionAnchor(
                                ModelUtil.getSourceRef(linkView));
                coveredBy.add(new SetEntry(anchor.getLocation(null).y, true,
                        (OccurrenceSpecification) ModelUtil.<Message> getSemantic(linkView)
                                .getSendEvent()));
            } else if (linkView.getAnchorTarget() != null
                    && ModelUtil.getSemantic(linkView.getAnchorTarget()) == lifeline) {
                final ConnectionAnchor anchor =
                        getEditPartFor(linkView.getAnchorTarget()).getConnectionAnchor(
                                ModelUtil.getTargetRef(linkView));

                coveredBy.add(new SetEntry(anchor.getLocation(null).y, false,
                        (OccurrenceSpecification) ModelUtil.<Message> getSemantic(linkView)
                                .getReceiveEvent()));
            }
        }

        return coveredBy;
    }

    private boolean usesLocalCoords(final DiagramElement element) {
        return !neIsInstance(element, CombinedFragment.class);
    }

    public static class SetEntry implements Comparable<SetEntry> {
        private final int coordinate; /* Absolute */
        private final boolean send;
        private final InteractionFragment fragment;

        public SetEntry(final int coordinate, final boolean send, final InteractionFragment fragment) {
            this.coordinate = coordinate;
            this.send = send;
            this.fragment = fragment;
        }

        public InteractionFragment getFragment() {
            return fragment;
        }

        /** Gives the abolute y-coordinate of the fragment. */
        public int getCoordinate() {
            return coordinate;
        }

        public int compareTo(SetEntry o) {
            int diff = coordinate - o.coordinate;
            if (diff != 0)
                return diff;
            else {
                return send ? 1 : -1;
            }
        }
    }
}
